package com.avapir.snake.Actors.PC;

import java.util.Vector;

import com.avapir.snake.Actors.NPC.Food;
import com.avapir.snake.Actors.NPC.Food.FoodType;
import com.avapir.snake.Core.Painter;
import com.avapir.snake.UI.Game;

/**
 * Simple AI for snakes It computes behavior for only one snake at one time<br>
 * so I use cycle for all AI-controlled snakes
 * 
 * @author Alpen Ditrix
 */
public class AI {

	/**
	 * Link to snake, which behavior is computing now
	 */
	private static Snake snake;

	/**
	 * It's direction, with which class works and which will be given to snake
	 */
	private static Direction dir;

	/**
	 * We must know it, because snakes unable changing direction to opposite<br>
	 * in one move
	 */
	private static Direction currentDir;

	private static final double SCALER_BIG = 0.3333333d;
	private static final double SCALER_RAGNARÖK = 100;
	private static final double SCALER_VALHALLA = 0;
	private static final double SCALER_INVULNERABILITY = 0.1d;

	/**
	 * Method paves shortest way without wall-wraps and correct if it is suicide
	 * 
	 * @param s
	 *            snake, which will be directed
	 * @param curDir
	 *            current direction of {@code snake}. Look {@code curredDir} for
	 *            more information
	 * @return
	 */
	public static Direction paveWayFrom(Snake s, Direction curDir) {
		snake = s;
		currentDir = curDir;
		int x = snake.getHeadX(), y = snake.getHeadY();

		int[] location = { x, y };
		getDirection(location, getTargetFrom(x, y));

		correction();

		// battleLog.println("AI manages " + s + "go to "+ dir);
		return dir;
	}

	/**
	 * No one snake want to eat yourself
	 * 
	 * @return
	 */
	private static boolean checkForSuicide() {
		int[] offset = getOffset();
		int offsetX = offset[0];
		int offsetY = offset[1];

		if (snake.isCovering(snake.getHeadX() + offsetX, snake.getHeadY()
				+ offsetY) != -1) {
			dir = nextDir();
			return true;
		} else
			return false;
	}

	/**
	 * At first glance, here is some magic, but it's O.K.
	 * <p>
	 * At first I checking "if I going to eat myself?" If it's true,
	 * {@code checkForSuicide} method will give me "next" direction and will
	 * return {@code true}. So cycle willn't broken and will continue, while
	 * {@code false} value will not be returned, so cycle will break or all the
	 * <B>four</b> (const "4" from cycles amount got from here) available
	 * directions
	 * 
	 */
	private static void correction() {
		for (int i = 0; i < 4; i++) {
			if (!checkForSuicide())
				break;
		}
		for (int i = 0; i < 4; i++) {
			if (!checkForWall()) {
				break;
			}
		}
	}

	private static boolean checkForWall() {
		int[] offset = getOffset();
		int offsetX = offset[0];
		int offsetY = offset[1];
		boolean out = false;

		Vector<Food> food = Game.getFood();
		for (int i = 0; i < food.size(); i++) {
			Food thiss = food.get(i);
			if (thiss.getType() == FoodType.WALL) {
				if (thiss.getX() == snake.getHeadX() + offsetX
						&& thiss.getY() == snake.getHeadY() + offsetY) {
					dir = nextDir();
					out = true;
					System.err.print("Turned");
					break;
				}
			}
		}
		return out;
	}

	/**
	 * Calculates shortest non-wrap way from {@code location} to {@code target} <br>
	 * Obviously it appears as ladder approximated to straight line
	 * 
	 * @param location
	 *            current position of snake
	 * @param target
	 *            position of food piece chosen as target
	 */
	private static void getDirection(int[] location, int[] target) {
		int x = location[0];
		int target_x = target[0];
		int y = location[1];
		int target_y = target[1];

		if (target_x != x) {
			float tan = (y - target_y) / (target_x - x);
			if (target_x > x) {
				if (tan > 1)
					dir = Direction.setUp(currentDir);
				else if (tan <= -1)
					dir = Direction.setDown(currentDir);
				else
					dir = Direction.setRight(currentDir);
			} else {
				if (tan > 1)
					dir = Direction.setDown(currentDir);
				else if (tan <= -1)
					dir = Direction.setUp(currentDir);
				else
					dir = Direction.setLeft(currentDir);
			}
		} else {
			if (target_y < y)
				dir = Direction.setUp(currentDir);
			else
				dir = Direction.setDown(currentDir);
		}
	}

	/**
	 * @return difference between snake's position before and after turn in
	 *         chosen direction
	 */
	private static int[] getOffset() {
		int offsetX = 0, offsetY = 0;

		switch (dir) {
		case RIGHT:
			offsetX = Painter.getCellSize();
			break;
		case UP:
			offsetY = -Painter.getCellSize();
			break;
		case LEFT:
			offsetX = -Painter.getCellSize();
			break;
		case DOWN:
			offsetY = Painter.getCellSize();
			break;
		}

		int[] out = { offsetX, offsetY };
		return out;
	}

	/**
	 * Counts actual distances to all food pieces scaling it with predefined
	 * modifiers and at the same time counts the minimum of them
	 * 
	 * 
	 * @param x
	 *            snake's head X position
	 * @param y
	 *            snake's head Y position
	 * @return
	 */
	private static int[] getTargetFrom(int x, int y) {
		int target_x = 0, target_y = 0;
		double distance = Integer.MAX_VALUE;

		for (Food f : Game.getFood()) {
			try {
				double newDistance = Math.sqrt((f.getX() - x) * (f.getX() - x)
						+ (f.getY() - y) * (f.getY() - y));

				// Applying modifiers of priority

				switch (f.getType()) {
				case BIG:
					newDistance *= SCALER_BIG;
					break;
				case WALL:
					newDistance *= SCALER_RAGNARÖK;
					break;
				case VALHALLA:
					newDistance *= SCALER_VALHALLA;
					break;
				case INVULNERABILITY:
					newDistance *= SCALER_INVULNERABILITY;
				}
				if (newDistance < distance) {
					target_x = f.getX();
					target_y = f.getY();
					distance = newDistance;
				}
			} catch (Exception e) {
				System.out.println(f);
				throw new RuntimeException();
			}

		}

		int[] out = { target_x, target_y };
		return out;
	}

	/**
	 * Turns direction CCW
	 * 
	 * @return new direction
	 */
	private static Direction nextDir() {
		switch (dir) {
		case RIGHT:
			return Direction.UP;
		case UP:
			return Direction.LEFT;
		case LEFT:
			return Direction.DOWN;
		case DOWN:
			return Direction.RIGHT;
		default:
			return Direction.STOP;
		}
	}
}